package edu.ufl.cise.cop5555.sp12;


import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import edu.ufl.cise.cop5555.sp12.TokenStream;
import edu.ufl.cise.cop5555.sp12.TokenStream.Token;
import static edu.ufl.cise.cop5555.sp12.Kind.*;


public class Scanner {
	
	private enum State {
		START, GOT_EQUALS, IDENT_PART, GOT_ZERO, DIGITS, EOF, GOT_LESSTHAN, GOT_NOT, GOT_GREATTHAN, GOT_QUOTE,
		GOT_BACK, GOT_BACKSLASH, GOT_HASH, COMMENT_START, CLOSE_HASH1
		
	}
	
	private State state;

	char ch;
	final TokenStream stream;
	int begOffset ;
	int eof;
	private int index;//next char to process during scanning, or if none, past end of the array
	
	HashMap< String, Kind> map = new HashMap<String, Kind>();
	
	
	
	public void populateKeyBool()
	{
		//PROG, GORP, STRING, INT, BOOLEAN, MAP, IF, ELSE, FI, DO, OD, PRINT, PRINTLN, 
		
		map.put("PROG", PROG);
		map.put("GORP", GORP);
		map.put("STRING", STRING);
		map.put("INT", INT);
		map.put("BOOLEAN", BOOLEAN);
		map.put("MAP", MAP);
		map.put("IF", IF);
		map.put("ELSE", ELSE);
		map.put("FI", FI);
		map.put("DO", DO);
		map.put("OD", OD);
		map.put("PRINT", PRINT);
		map.put("PRINTLN", PRINTLN);
		map.put("TRUE", BOOLEAN_LITERAL);
		map.put("FALSE", BOOLEAN_LITERAL);
		
	}
	public Scanner(TokenStream stream)  {
		//TODO:  IMPLEMENT ME
		this.stream = stream;
		index =0;
		eof =0;
		try {
			getch();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
	populateKeyBool();
	
		
	}
	
	private void getch() throws IOException{
		
		//get next char from tokenstream and update index
		int size = this.stream.inputChars.length;
		if(index < size)
		{
			ch = this.stream.inputChars[index++];
		//System.out.println("char "+ ch);
		}
		else
		{
			if( eof  == 0) 
				index++;
			ch = '\0';
		}
		
	}
	
	public Token next() throws IOException, NumberFormatException
	{
		state = State.START;
		
		Token t = null;
		do{
		
			switch(state)
			{
			case START:
					begOffset = index-1;
								
					switch(ch)
					{
					case '\0' : 
						state = State.EOF;
						eof =1;
						break;
					case ' ' : case '\t' : case '\n' : case '\f' : case '\r' :
						break; // white space
					case '=':
						state = State.GOT_EQUALS;
						break;
					case '+':
						t = stream.new Token(Kind.PLUS,begOffset,index);
						break;
					case '/':
						t = stream.new Token(Kind.DIVIDE,begOffset,index);
						break;
					case '&' :
						t = stream.new Token(AND, begOffset, index);
						break;
					case '|' :
						t = stream.new Token(OR, begOffset, index);
						break;
					case '-':
						t = stream.new Token(MINUS,begOffset,index);
						break;
					case '*' :
						t= stream.new Token(Kind.TIMES, begOffset, index);
						break;
					case '<' :
						state = State.GOT_LESSTHAN;
						break;
					case  '>' : 
						state = State.GOT_GREATTHAN;
						break;
					case '!' :
						state = State.GOT_NOT;
						break;
					case '\"':
						state = State.GOT_QUOTE;
						break;
					case '.' : 
						t= stream.new Token(Kind.DOT, begOffset, index);
						break;
					case ';':
						t= stream.new Token(Kind.SEMI, begOffset, index);
						break;
					case ',' :
						t= stream.new Token(Kind.COMMA, begOffset, index);
						break;
					case '(' :
						t= stream.new Token(Kind.LEFT_PAREN, begOffset, index);
						break;
					case ')' :
						t= stream.new Token(Kind.RIGHT_PAREN, begOffset, index);
						break;
					case '[' :
						t= stream.new Token(Kind.LEFT_SQUARE, begOffset, index);
						break;
					case ']' :
						t= stream.new Token(Kind.RIGHT_SQUARE, begOffset, index);
						break;
					case '{' :
						t= stream.new Token(Kind.LEFT_BRACE, begOffset, index);
						break;
					case '}' :
						t= stream.new Token(Kind.RIGHT_BRACE, begOffset, index);
						break;
					case ':' :
						t= stream.new Token(Kind.COLON, begOffset, index);
						break;
					case '0':
						state = State.GOT_ZERO;
						break;
					case 26 : // ctrl - z
						state = State.EOF;
						break;
					//case '\\':
						//state = State.GOT_BACKSLASH;
						//break;
					case '#' :
						state = State.GOT_HASH;
						break;
					default : //aplha and num literals
						if(Character.isDigit(ch))
						{
							state = State.DIGITS;
							
						}
						else if(Character.isJavaIdentifierStart(ch))
						{
							state = State.IDENT_PART;
						
						}
						else
						{
							t= stream.new Token(ILLEGAL_CHAR, begOffset, index);
							//TODO error
						}
											
					} //end inner switch
					getch();
					break;	
			
			case GOT_HASH:
				switch(ch)
				{
				case '\0' : 
					t= stream.new Token(MALFORMED_COMMENT, begOffset, index-1);
					index--;
					break;
				case '#' : 
					state = State.COMMENT_START;
					break;
				default : 
					t= stream.new Token(MALFORMED_COMMENT, begOffset, index-1);
					index--;
					
				
				}			
				
			getch();
			break;
			
			case COMMENT_START:
				switch(ch)
				{
				case '\0' : 
					t= stream.new Token(MALFORMED_COMMENT, begOffset, index-1);
					index--;
					break;
				case '#' : 
					state = State.CLOSE_HASH1;
					break;
				default : 
					state = State.COMMENT_START;
								
				}
			
			getch();
			break;
			
			case CLOSE_HASH1 :
				switch(ch)
				{
				case '#':
					//Comment is legal
					state = State.START;
					break;
					
				default :
					state = State.COMMENT_START;
					
				
				}
			
			getch();
			break;
			/*case GOT_BACKSLASH: 
				switch(ch)
				{
				case 'n' : case 't' : case 'r' : case 'f' :
					state = State.START;
					break;
				default : 
				}
			getch();
			break;
			
			*/
			case GOT_LESSTHAN:
				switch(ch)
				{
				case '\0' : 
					t = stream.new Token(Kind.LESS_THAN, begOffset, index-1);
					break;
				case ' ' : case '\t' : case '\n' : case '\f' : case '\r' :
					t = stream.new Token(Kind.LESS_THAN, begOffset, index-1);
					index--;
					break;
				case '=' : 
					t = stream.new Token(AT_MOST, begOffset,index);
					break;
				default: 
					t = stream.new Token(Kind.LESS_THAN, begOffset, index-1);
					index--;			
				
				}
			getch();
			break;
			
			case GOT_GREATTHAN:
				switch(ch)
				{
				case '\0' : 
					t = stream.new Token(Kind.GREATER_THAN, begOffset, index-1);
					break;
				case ' ' : case '\t' : case '\n' : case '\f' : case '\r' :
					t = stream.new Token(Kind.GREATER_THAN, begOffset, index-1);
					index--;
					break;
				case '=' : 
					t = stream.new Token(AT_LEAST, begOffset,index);
					break;
				default: 
					t = stream.new Token(Kind.GREATER_THAN, begOffset, index-1);
					index--;			
				
				}
				
			getch();
			break;
			
			case GOT_NOT:
				switch(ch)
				{
				case '\0' : 
					t = stream.new Token(Kind.NOT, begOffset, index-1);
					break;
				case ' ' : case '\t' : case '\n' : case '\f' : case '\r' :
					t = stream.new Token(Kind.NOT, begOffset, index-1);
					index--;
					break;
				case '=' : 
					t = stream.new Token(NOT_EQUALS, begOffset,index);
					break;
				default: 
					t = stream.new Token(Kind.NOT, begOffset, index-1);
					index--;			
				
				}
				
			getch();
			break;
			
			
				
			case GOT_ZERO: 
				switch(ch)
				{
				case '\0':
					state = State.EOF;
					t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
					eof =1;
					break;
				case ' ': case '\n' : case '\r' : case '\f' : case '\t':
					t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
					break;
				default :
					if(Character.isDigit(ch))
					{
						//t = stream.new Token(ILLEGAL_CHAR, begOffset, index);
						t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
						index--;
						//error
					}
					else 
					{
						t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
						index--;
						
					}
				
				}
					
				getch();	
				break;
			case DIGITS:
				switch (ch)
				{
				case '\0':
					t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
					//state = State.EOF;
					eof =1;
					break;
				case ' ': case '\n' : case '\r' : case '\f' : case '\t':
					t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
					break;
				default:
					if(Character.isDigit(ch))
					{
					state = State.DIGITS;
					
					}
					else
						//TODO 
						{
						t = stream.new Token(INTEGER_LITERAL, begOffset, index-1);
						index--;
						//state=State.START;
						}
						
						
				
				}
				
				getch();
				break;
			case IDENT_PART:
				switch(ch)
				{
				 case '\0':
					 state = State.EOF;
					 eof =1;
					 /*
					  * Before making it an ident.. check if it is a keyword or bool literal
					  * 
					  * */
					 StringBuffer sb = new StringBuffer();
					 
				for(int i = begOffset; i < index-1;i++)
					sb.append(this.stream.inputChars[i]);
				if(map.containsKey(sb.toString().toUpperCase()))
					t = stream.new Token(map.get(sb.toString().toUpperCase()), begOffset, index-1); // exists in the map. Make a
				else	
					 t = stream.new Token(Kind.IDENTIFIER, begOffset, index-1);
					 break;	 	 
				case ' ': case '\t' : case '\n' : case '\f' : case '\r':
					 StringBuffer sb1 = new StringBuffer();
				 
						for(int i = begOffset; i < index-1;i++)
							sb1.append(this.stream.inputChars[i]);
						if(map.containsKey(sb1.toString().toUpperCase()))
							t = stream.new Token(map.get(sb1.toString().toUpperCase()), begOffset, index-1); // exists in the map. Make a
						else	
							t = stream.new Token(Kind.IDENTIFIER, begOffset, index-1);
						
					break;
				default : 
					if(Character.isJavaIdentifierPart(ch))
						state = State.IDENT_PART;
					else 
					{
						 StringBuffer sb2 = new StringBuffer();
						 
							for(int i = begOffset; i < index-1;i++)
								sb2.append(this.stream.inputChars[i]);
							if(map.containsKey(sb2.toString().toUpperCase()))
								t = stream.new Token(map.get(sb2.toString().toUpperCase()), begOffset, index-1); // exists in the map. Make a
							else							
								t = stream.new Token(Kind.IDENTIFIER, begOffset, index-1);
					 state = State.START;
					 index--;
					}
					
								
				
				}
			getch();
			break;
			case GOT_EQUALS:
				switch(ch)
				{
				case '\0':
					t = stream.new Token (ASSIGN,begOffset,index-1);
					break;
				case '=': 
					t = stream.new Token(EQUALS,begOffset, index);
					
					break;
				default : 
					t = stream.new Token (ASSIGN,begOffset,index-1);
					index--;
				
				}
				getch();
				break;
			case GOT_QUOTE:
				switch(ch)
				{
				case '\0' : 
					t = stream.new Token(MALFORMED_STRING, begOffset, index-1);
					index--;
					break;
				case ' ' : case '\t' : case '\f' :					
					break;
				case '\\' :
					state = State.GOT_BACK;
					break;
				case '\n' : case '\r' :
					t = stream.new Token(MALFORMED_STRING, begOffset, index-1);
					index--;
					break;
				case '"':
					t= stream.new Token (STRING_LITERAL,begOffset,index);
					break;
				default:
					state = State.GOT_QUOTE;
				
				}
			getch();
			break;
			case GOT_BACK:
			switch(ch)
			{
			case 't' : case 'n' : case 'r' : case 'f' :case '"' : case '\\' :
				state = State.GOT_QUOTE;
				break;
			
				
			default:
				t = stream.new Token(MALFORMED_STRING, begOffset, index-1);
				index--;
				break;
				
			
			}
				
			getch();
			break;
			case EOF:
				t = stream.new Token (EOF,begOffset,index-1);
				break;
			default :		
			assert false : "Should not reach here";
			} // end main switch
			
			
		}while(t == null);
		
		
		return t;
		
	} // end fucntion
	
	public void scan() {
	Token t = null;
	do{
		try {
			t = next();
			stream.tokens.add(t);
			
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
}while(!t.kind.equals(EOF));
	}

   //You will need to add fields and additional methods
	
}
